
class Controller < MySingleton

  @@callback ||= {}
  @@path ||= {}

  attr_accessor :layout, :path

  def self.test_plug path
    path = path.to_s
    instance.path = @@path[self] = path
  end

  def self.plug path
    path = path.to_s

    #changes subscription
    if @@callback[self]      
      RequestEvent.unsubscribe(RequestEvent, 'url:'+@@path[self].to_s, &@@callback[self])
    end
    @@callback[self] = lambda { |e| self.get_response e }

    RequestEvent.on('url:'+path, @@callback[self])
    instance.path = @@path[self] = path
  end

  def self.layout layout
    instance.layout = layout
  end

  def self.get_response e
    e.respond(:html => go(e) || 'no response given')
  end

  def self.go e
    instance.go e
  end

  def go e
    if e.params['json'] && !e.data['json_event']
      event = JSON.parse(e.params['json'])
      e.data['json_event'] = Event.new(event['name'], event['params'])
      e.data['json_event'].data['processed'] = false
      e.data['json_event'].data['params'] = e.params
      e.env[:layouted] = true
    end

    if e.data['json_event']
      begin
        if result = process_ajax_event(e.data['json_event'])
          e.data['json_event'].data['processed'] = true

          if file = (result[0].data.delete 'file')
            headers = (result[0].data.delete 'headers') || {}
            headers.merge! 'X-bublifuk-json' => result[0].data.to_json
            #p headers
            e.respond({ :response => Rack::Response.new(file, 200, headers) })
          else
            data = result.map(&:to_hash).to_json
            e.respond({
              :response => Rack::Response.new(data)
            })
          end

          # this workaround was needed on linux
          data = result.map(&:to_hash).to_json
          e.respond({
            :response => Rack::Response.new(data)
          })
        end
      rescue Exception => e
        return e.to_s+"\n  "+e.backtrace.join("\n  ")
      end
    end
    
    action = get_action(e)
    e.data['controller'] = self

    if !methods.include? action
      e.env[:unprocessed_tail] << action
      action = 'index'
    end

    e.env[:actual_path] = (e.env[:tail] - e.env[:unprocessed_tail]).join('/')
    e.env[:action] = action
    html = wrap e

    if e.data['json_event'] && !e.data['json_event'].processed
      raise "Event was not processed: #{e.data['json_event'].inspect}."
    end
    
    if @layout && !e.env[:layouted]
      e.env[:layouted] = true
      e.data['content'] = html
      @layout.render e
    else
      html
    end
  end

  def wrap e
    send(e.env[:action], e)
  end

  def process_ajax_event e
    method_name = "on_"+e.name
    if methods.include? method_name
      send(method_name, e)
      e.response
    else
      return false
    end
  end

  def get_action e
    if !e.env[:tail]
      e.env[:tail] = get_tail(e.env['REQUEST_URI'])
      e.env[:unprocessed_tail] = e.env[:tail].dup
    end

    #p e.env[:unprocessed_tail][0], @path, self

    if e.env[:unprocessed_tail][0] == @path
      e.env[:unprocessed_tail].shift
    end

    e.env[:controller_path] = (e.env[:tail] || []) - (e.env[:unprocessed_tail] || [])

    #e.env[:actual_path] = (e.env[:tail] - e.env[:unprocessed_tail]).join('/')
#    puts @path.inspect
#    puts "#{e.env[:tail].inspect}: #{e.env[:unprocessed_tail].inspect}"

    e.env[:unprocessed_tail].shift || 'index'
  end

  def get_tail uri
    tail = uri.split('?').first.split('/')
    tail.shift #gets rid of first ''
    tail
  end

  def link subpage, e
    subpage = subpage.to_s
    if subpage == 'index'
      subpage = ''
    end
    if e.env[:actual_path] == ''
      '/'+subpage
    else
      #p e.env[:actual_path], subpage.to_s
      '/'+File.join(e.env[:controller_path], subpage)
    end
  end

  def shift_tail e
    e.env[:unprocessed_tail].shift
  end


  class Test < FwTest

    class AdminController < Controller
      test_plug 'admin'

      def logs e
        'hello from logs'
      end
    end

    def test_process_single_thing
      c = AdminController.instance
      result = c.go  RequestEvent::Mock.new('env' => {'REQUEST_URI' => '/admin/logs/'})
      assert_equal result, 'hello from logs'
    end


    class AdminController2 < Controller
      test_plug 'admin'

      def logs e
        LogsController.go e
      end
    end

    class LogsController < Controller
      def index e
        'hello from logs, we give you free hugs'
      end

      def create e
        'create a new log'
      end
    end


    def test_process_nested_thing
      c = AdminController2.instance
      result = c.go  RequestEvent::Mock.new('env' => {'REQUEST_URI' => '/admin/logs/'})
      assert_equal result, 'hello from logs, we give you free hugs'
    end

    def test_process_nested_thing_with_non_default_action
      c = AdminController2.instance
      result = c.go  RequestEvent::Mock.new('env' => {'REQUEST_URI' => '/admin/logs/create'})
      assert_equal result, 'create a new log'
    end

    def test_link
      c = AdminController.instance
      e = RequestEvent::Mock.new('env' => {'REQUEST_URI' => '/admin/logs/'})
      c.go e
      assert_equal c.link('subpage', e), '/admin/subpage'
    end

    def test_link_with_symbol
      c = AdminController.instance
      c.go e = RequestEvent::Mock.new('env' => {'REQUEST_URI' => '/admin/logs/'})
      assert_equal c.link(:subpage, e), '/admin/subpage'
    end

    def test_link_on_nested_controller
      e = RequestEvent::Mock.new('env' => {'REQUEST_URI' => '/admin/logs/'})
      AdminController2.go e

      eq e.controller.class, LogsController
      eq e.link('subpage'), '/admin/logs/subpage'
    end

  end

  class Test::JSON < FwTest

    class ControllerWithJsonAction < Controller
      attr_accessor :called

      def index e
        #raise 'Index should not be called'
      end

      def on_increment e
        e.respond( 'result' => e.number.to_i + 1 )
        'we are not using return value'
      end

      def on_json_action e
        @called = true
      end
    end

    def test_process_ajax_event_calls_json_action
      c = ControllerWithJsonAction.new
      c.process_ajax_event(Event.new('json_action'))
      assert c.called
    end


    class ControllerWithNoJsonAction < Controller
      def index e

      end
    end

    
    def test_raise_when_nobody_wants_event
      c = ControllerWithNoJsonAction.new
      assert_raise(RuntimeError) {
        c.go(
          RequestEvent::Mock.new(
            'params' => {
              'json' => ({
                'name'   => 'action',
                'params' => {}
              }.to_json)
            }))
      }
    end

    def test_process_ajax_returns_response_event_with_params_from_method_result
      c = ControllerWithJsonAction.new
      e = Event.new('increment', 'number' => 5)
      r = c.process_ajax_event(e).first
      eq r.name, 'increment_response'
      eq r.data, 'result' => 6
    end

    def test_json_events_integration
      c = ControllerWithJsonAction.new
      
      c.go(e = RequestEvent::Mock.new(
          'params' => {
            'json' => ({
              'name' => 'increment',
              'params' => {
                'number' => 5
              }
            }).to_json
          }
        ))

      json_response = e.response.first.data['response'].body[0]

      eq JSON.parse(json_response), [{
        'name' => 'increment_response',
        'result' => 6
      }]
    end
  end
end